package com.beiding.hotcode;

import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

class HotInstance extends MyInstance {

    private final JavaMonitor javaMonitor;

    private Object[] params;

    private Class[] paramTypes;

    private String paramTypesString;

    private MyClass myClass;

    HotInstance(MyClass myClass, JavaMonitor javaMonitor, Object[] params) {
        this.myClass = myClass;
        this.javaMonitor = javaMonitor;
        this.params = params;
        this.paramTypesString = createParamTypesString(params);
        //  init();
    }

    @Override
    public MyClass getMyClass() {
        return myClass;
    }

    public Class getType() {
        checkChange();
        return type;
    }

    @Override
    public <T> MyMethod<T> method(String name, Class<T> type) {
        return params -> (T) HotInstance.this.invoke(name, params);
    }


    private String createParamTypesString(Object[] params) {

        StringBuilder builder = new StringBuilder();

        if (params.length > 0) {
            builder.append(params[0] == null ? "null" : params[0].getClass().getName());
            for (int i = 1; i < params.length; i++) {
                builder.append(",").append(params[i] == null ? "null" : params[i].getClass().getName());
            }
        }

        return builder.toString();

    }

    //实例
    private Object instance;

    private void makeMethodParamMap() {
        methods.clear();
        Class<?> aClass = instance.getClass();
        for (Method method : aClass.getMethods()) {
            Class<?>[] parameterTypes = method.getParameterTypes();
            Map<Integer, List<MethodHolder>> integerListMap = methods.computeIfAbsent(method.getName(), k -> new HashMap<>());
            List<MethodHolder> methodHolders = integerListMap.computeIfAbsent(parameterTypes.length, k -> new ArrayList<>());
            MethodHolder methodHolder = new MethodHolder(method, parameterTypes);
            methodHolders.add(methodHolder);
        }

    }

    private Method getMethod(String method, Object... params) {
        Map<Integer, List<MethodHolder>> integerListMap = methods.get(method);
        if (integerListMap == null) {
            return null;
        }
        List<MethodHolder> methodHolders = integerListMap.get(params.length);
        if (methodHolders == null) {
            return null;
        }

        Method m = null;
        for (MethodHolder methodHolder : methodHolders) {
            if (methodHolder.match(params)) {
                if (m != null) {
                    throw new RuntimeException("无法确定方法:" + method + "(" + createParamTypesString(params) + ")");
                }
                m = methodHolder.getMethod();
            }
        }

        if (m == null) {
            throw new RuntimeException("未找到方法:" + method + "(" + createParamTypesString(params) + ")");
        }

        return m;
    }

 /*   private void init() {
        if (javaMonitor.checkChange()) {
            javaMonitor.reloadClass();
        }
        newInstance();
    }*/

    public Object getInstance() {
        checkChange();
        return instance;
    }

    private void newInstance() {
        Class theClass = javaMonitor.getTheClass();
        if (theClass == null) {
            System.out.println("无法获取类");
            return;
        }
        type = theClass;
        try {

            Constructor[] constructors = theClass.getConstructors();

            Constructor constructor = null;

            out:
            for (Constructor c : constructors) {
                Class[] parameterTypes = c.getParameterTypes();
                if (parameterTypes.length != this.params.length) {
                    continue;
                }
                for (int i = 0; i < parameterTypes.length; i++) {
                    if (params[i] == null) {
                        continue;
                    }

                    if (!parameterTypes[i].isInstance(this.params[i])) {
                        continue out;
                    }

                }
                if (constructor != null) {
                    throw new RuntimeException("无法确定构造函数:" + theClass.getName() + "(" + paramTypesString + ")");
                }
                constructor = c;
            }
            if (constructor == null) {
                throw new RuntimeException("未找到构造函数:" + theClass.getName() + "(" + paramTypesString + ")");
            }

            paramTypes = constructor.getParameterTypes();

            //先遗弃原来的
            instanceLifecycle.beforeAbandon(instance, methods);

            instance = constructor.newInstance(params);

            makeMethodParamMap();

            //创建后再调用新的
            instanceLifecycle.afterCreate(instance, methods);

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public Class[] getParamTypes() {
        return paramTypes;
    }

    public Object[] getParams() {
        return params;
    }

    private Class type;

    //执行检查操作
    private void checkChange() {

        if (instance == null) {
            if (javaMonitor.checkChange()) {
                synchronized (javaMonitor) {
                    if (javaMonitor.checkChange()) {
                        javaMonitor.reloadClass();
                    }
                }
            }
            newInstance();
            return;
        }

        //多线程问题
        if (javaMonitor.checkChange()) {

            //单线程重载
            synchronized (javaMonitor) {
                if (javaMonitor.checkChange()) {
                    javaMonitor.reloadClass();
                }
            }

            if (type != javaMonitor.getTheClass()) {
                synchronized (this) {
                    if (type != javaMonitor.getTheClass()) {
                        //多个实例使用同一个对象

                        try {

                            Object o = instance;

                            newInstance();

                            Object newInstance = instance;

                            //找到所有的内部数据,进行复制操作
                            Field[] declaredFields = newInstance.getClass().getDeclaredFields();
                            for (Field declaredField : declaredFields) {

                                int modifiers = declaredField.getModifiers();

                                //如果不是final的才进行值复制
                                if (!Modifier.isFinal(modifiers)) {
                                    declaredField.setAccessible(true);

                                    try {
                                        Field field = o.getClass().getDeclaredField(declaredField.getName());
                                        field.setAccessible(true);
                                        Object v = field.get(o);
                                        if (v == null) {
                                            //有必要赋值即使是null
                                            declaredField.set(newInstance, null);
                                        } else {
                                            if (declaredField.getType() == v.getClass()) {
                                                declaredField.set(newInstance, v);
                                            }
                                        }
                                    } catch (NoSuchFieldException ignore) {

                                    }
                                }
                            }

                            //如果父类相同,那么可以从父类开始递归复制内部属性
                            if (newInstance.getClass().getSuperclass() == o.getClass().getSuperclass()) {
                                Class t = newInstance.getClass().getSuperclass();
                                if (t != null) {
                                    while (t != Object.class && t != null) {
                                        Field[] fields = t.getDeclaredFields();
                                        if (fields != null) {
                                            for (Field field : fields) {
                                                field.setAccessible(true);
                                                field.set(newInstance, field.get(o));
                                            }
                                        }
                                        t = t.getSuperclass();
                                    }
                                }

                            }

                            instance = newInstance;
                            makeMethodParamMap();

                        } catch (Exception e) {
                            throw new RuntimeException(e);
                        }

                        type = javaMonitor.getTheClass();
                    }
                }
            }
        }

    }

    //发起调用操作
    public Object invokeCatch(String method, Object... params) throws Throwable {
        checkChange();
        try {
            Method m = getMethod(method, params);
            if (m == null) {
                throw new NoSuchMethodException(method + "(" + createParamTypesString(params) + ")");
            }
            return m.invoke(instance, params);
        } catch (InvocationTargetException e) {

            //抛出所有异常
            throw e.getTargetException();

        } catch (Exception e) {

            throw new RuntimeException(e);
        }
    }

    public void set(String field, Object value) {
        checkChange();
        try {
            Field f = instance.getClass().getField(field);
            f.set(instance, value);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void forceSet(String field, Object value) {
        checkChange();
        try {
            Field f = instance.getClass().getDeclaredField(field);
            f.setAccessible(true);
            f.set(instance, value);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public Object get(String field) {
        checkChange();
        try {
            Field f = instance.getClass().getDeclaredField(field);
            return f.get(instance);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public Object forceGet(String field) {
        checkChange();
        try {
            Field f = instance.getClass().getField(field);
            f.setAccessible(true);
            return f.get(instance);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

}
